#include "utf8.h"

// 获取UTF-8字符的字节长度
int UTF8GetCharLen(const uint8_t c)
{
    if ((c & 0x80) == 0x00)
        return 1; // 1字节字符
    if ((c & 0xE0) == 0xC0)
        return 2; // 2字节字符
    if ((c & 0xF0) == 0xE0)
        return 3; // 3字节字符
    if ((c & 0xF8) == 0xF0)
        return 4; // 4字节字符
    return -1;
}

// 检查一个字节是否是 UTF-8 字符的开始字节
int UTF8IsStartByte(const uint8_t byte)
{
    return (byte & 0xC0) != 0x80; // 0xC0 = 11000000, 0x80 = 10000000
}

// 查找指定位置前一个字符的起始位置
size_t UTF8FindPreviousCharStartPos(const uint8_t *str, size_t position)
{
    if (position <= 0 || str == NULL) {
        return -1; // 无效位置
    }

    int i = position - 1; // 从给定位置的前一个字符开始检查
    while (i >= 0 && !UTF8IsStartByte((unsigned char)str[i])) {
        i--; // 向前寻找字节序列的开始字节
    }

    if (i >= 0) {
        return i; // 返回前一个字符的起始位置
    }

    return position; // 没有找到前一个字符
}

uint8_t UTF8GetCharShowWidth(uint8_t c)
{
    int len = UTF8GetCharLen(c);
    uint8_t width = (len > 1) ? 2 : 1;
    return width;
}

uint32_t UTF8GetStrShowWidth(const uint8_t *str)
{
    if (!str) {
        return 0;
    }
    uint32_t width = 0;
    while (*str) {
        int len = UTF8GetCharLen(*str);
        width += UTF8GetCharShowWidth(*str);
        str += len;
    }
    return width;
}

size_t UTF8FindEndPosForContainerWidth(const uint8_t *str, size_t startPos, uint32_t containerWidth)
{
    uint32_t currentWidth = 0;
    size_t i = startPos;

    while (str[i] && currentWidth < containerWidth) {
        int charLen = UTF8GetCharLen(str[i]);
        uint8_t charWidth = UTF8GetCharShowWidth(str[i]);
        if (currentWidth + charWidth > containerWidth) {
            break; // 如果加上当前字符会超出容器宽度，则停止
        }
        currentWidth += charWidth;
        i += charLen;
    }
    return i; // 返回满足容器宽度的最大结束位置
}

uint32_t UTF8GetShowLengthByStartAndEndPos(const uint8_t *str, size_t startPos, size_t endPos)
{
    uint32_t length = 0;
    size_t i = startPos;
    while (str[i] && i < endPos) {
        int charLen = UTF8GetCharLen(str[i]);
        uint8_t charWidth = UTF8GetCharShowWidth(str[i]);
        length += charWidth;
        i += charLen;
    }
    return length;
}

void UTF8Encode(uint32_t codepoint, uint8_t *str)
{
    if (codepoint <= 0x7F) {
        // 1 byte: 0xxxxxxx
        str[0] = (uint8_t)codepoint;
        str[1] = '\0';
    } else if (codepoint <= 0x7FF) {
        // 2 bytes: 110xxxxx 10xxxxxx
        str[0] = (uint8_t)(0xC0 | (codepoint >> 6));
        str[1] = (uint8_t)(0x80 | (codepoint & 0x3F));
        str[2] = '\0';
    } else if (codepoint <= 0xFFFF) {
        // 3 bytes: 1110xxxx 10xxxxxx 10xxxxxx
        str[0] = (uint8_t)(0xE0 | (codepoint >> 12));
        str[1] = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F));
        str[2] = (uint8_t)(0x80 | (codepoint & 0x3F));
        str[3] = '\0';
    } else if (codepoint <= 0x10FFFF) {
        // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        str[0] = (uint8_t)(0xF0 | (codepoint >> 18));
        str[1] = (uint8_t)(0x80 | ((codepoint >> 12) & 0x3F));
        str[2] = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F));
        str[2] = (uint8_t)(0x80 | (codepoint & 0x3F));
        str[4] = '\0';
    }
}

uint32_t UTF8Decode(const char *str)
{
    unsigned char *byteStr = (uint8_t *)str;
    uint32_t codepoint = 0;
    
    if ((byteStr[0] & 0x80) == 0) {
        // 1 byte: 0xxxxxxx
        codepoint = byteStr[0];
    } else if ((byteStr[0] & 0xE0) == 0xC0) {
        // 2 bytes: 110xxxxx 10xxxxxx
        codepoint = (byteStr[0] & 0x1F) << 6;
        codepoint |= (byteStr[1] & 0x3F);
    } else if ((byteStr[0] & 0xF0) == 0xE0) {
        // 3 bytes: 1110xxxx 10xxxxxx 10xxxxxx
        codepoint = (byteStr[0] & 0x0F) << 12;
        codepoint |= (byteStr[1] & 0x3F) << 6;
        codepoint |= (byteStr[2] & 0x3F);
    } else if ((byteStr[0] & 0xF8) == 0xF0) {
        // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        codepoint = (byteStr[0] & 0x07) << 18;
        codepoint |= (byteStr[1] & 0x3F) << 12;
        codepoint |= (byteStr[2] & 0x3F) << 6;
        codepoint |= (byteStr[3] & 0x3F);
    }
    return codepoint;
}
